﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using MentalAlchemy.Atomics;
using MentalAlchemy.Atomics.Types;
using MentalAlchemy.Molecules.Image.Deprecated;
using MentalAlchemy.Molecules.MachineLearning;
using MentalAlchemy.Performance;
using MatrixMath = MentalAlchemy.Performance.MatrixMath;
using StructMath=MentalAlchemy.Atomics.StructMath;

namespace MentalAlchemy.Molecules.Image
{
	/// <summary>
	/// [molecule]
	/// 
	/// Class to represent constructible image description.
	/// </summary>
	[Serializable]
	public class ImageDescription
	{
		public const int DEFAULT_LOCAL_NEIGHBORHOOD_RADIUS = 2;

		private List<DescriptionEntry> entries = new List<DescriptionEntry>();

		#region - Properties. -
		/// <summary>
		/// Description entries.
		/// </summary>
		public List<DescriptionEntry> Entries
		{
			get { return entries; }
			set { entries = value; }
		}
		/// <summary>
		/// Name of the image (filename, ID, etc.).
		/// </summary>
		public string ImageName { get; set; }
		#endregion

		#region - Destruction. -
		//~ImageDescription()
		//{
		//    entries.Clear();
		//    if (ImageName != null) ImageName = "";
		//}
		#endregion

		#region - Public methods. -
		/// <summary>
		/// [molecule]
		/// 
		/// Converts existing entries into a list of features for each pixel which coordinates are included into [points].
		/// </summary>
		/// <param name="points">Array of pixels coordinates to consider.</param>
		/// <param name="header">Out string which contains name of pixel features.</param>
		/// <returns>List of pixel features vectors.</returns>
		public List<float[]> ConvertToPixelProperties(List<Point> points, out string header)
		{
			var temp = ImageDescriptionElements.ConvertToPixelPropertiesTable(entries, points, out header);
			return MentalAlchemy.Atomics.MatrixMath.ConvertToRowsList(temp);
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Converts existing entries into a list of features for all pixels.
		/// </summary>
		/// <param name="size">Image size in pixels.</param>
		/// <param name="header">Out string which contains name of pixel features.</param>
		/// <returns>List of pixel features vectors.</returns>
		public List<float[]> ConvertToPixelProperties(Size size, out string header)
		{
			var temp = ImageDescriptionElements.ConvertToPixelPropertiesTable(entries, size, out header);
			return Atomics.MatrixMath.ConvertToRowsList(temp);
		}

		/// <summary>
		/// Returns string containing names of pixel features in their consequetive order delimitered by tabulations.
		/// </summary>
		/// <returns>Header string.</returns>
		public string GetPixelFeaturesHeader()
		{
			return ImageDescriptionElements.GetPixelFeaturesHeader(entries);
		}

		/// <summary>
		/// Performs normalization of all table-type entries so that their values fall into [0; 1] range.
		/// </summary>
		public void NormalizeEntries()
		{
			foreach (var entry in entries)
			{
				if (entry.Values != null)
				{
					var temp = entry.Values;
					MatrixMath.NormalizeSelf(temp);
					entry.Values = temp;
				}
			}
		} 
		#endregion

		#region - Static methods. -
		/// <summary>
		/// Creates image description from the given bitmap with the specified description properties.
		/// </summary>
		/// <param name="img">Input bitmap.</param>
		/// <param name="props">Description properties.</param>
		/// <param name="pts">Output list of points, at which the description was taken. Empty if the whole image was used.</param>
		/// <returns>Image description.</returns>
		public static ImageDescription CreateFromBitmap(Bitmap img, ImageDescriptionProperties props, out List<Point> pts)
		{
			var log = LogWriter.Instance();
			log.Enabled = false;

			var res = new ImageDescription
			{
				ImageName = ""
			};

			var rand = new Random(DateTime.Now.Millisecond);
			//var img = new Bitmap(file);
			if (props.MaxImageDimension > 0)
			{
				log.WriteLine("ImageConversion.ResizeReduceOnly");
				img = ImageConversion.ResizeReduceOnly(img, props.MaxImageDimension);
			}

			log.WriteLine("ImageAnalysis.GetImagePoints");
			pts = ImageAnalysis.GetImagePoints(img, props.PointDetectorName, rand);

			#region - Calculate description elements. -
			res.Entries = pts.Count > 0
						?
						CalculateDescriptionElements(img, pts, props)
						:
						CalculateDescriptionElements(img, props);

			// perform normalization.
			log.WriteLine("NormalizeEntries");
			res.NormalizeEntries();
			#endregion

			return res;
		}

		/// <summary>
		/// Calculates elements for the given image at selected points.
		/// </summary>
		/// <param name="img">Input image.</param>
		/// <param name="pts">Points to calculate description features at.</param>
		/// <param name="props">Image description properties.</param>
		/// <returns>List of description entries.</returns>
		public static List<DescriptionEntry> CalculateDescriptionElements(Bitmap img, List<Point> pts, ImageDescriptionProperties props)
		{
			var log = LogWriter.Instance();
			var res = new List<DescriptionEntry>();

			var isGrayscale = ImageConversion.IsGrayscale(img);
			var grayImg = new Bitmap(img);
			if (!isGrayscale)
			{
				//log.WriteLine("ImageConversion.ConvertToGrayscale");
				ImageConversion.ConvertToGrayscale(grayImg);
			}

			log.WriteLine("Calculating features for selected image pixels");
			var ptsTable = Atomics.MatrixMath.CreateFromPoints(img.Height, img.Width, pts, 1.0f);
			// calculate image features at the given set of pixels.
			if (props.UseLocalBrightness || props.UseLocalColor)
			{
				//log.WriteLine("ImageConversion.ConvertToGrayscale");
				var color = ImageDescriptionElements.CalculateLocalMeans(img, ptsTable, props.Radius, isGrayscale);
				res.AddRange(color);
			}
			if (props.UseLocalBrightnessVariance)
			{
				//log.WriteLine("ImageConversion.ConvertToGrayscale");
				var var = ImageDescriptionElements.CalculateLocalBrightnessVariance(grayImg, ptsTable, props.Radius);
				res.Add(var);

				//var tempImg = ImageConversion.CreateBmpFromGrayscaleTable(var.Values);
				//tempImg.Save("img_var.png");
			}
			if (props.UseLocalBrightnessVariance)
			{
				//log.WriteLine("ImageConversion.ConvertToGrayscale");
				var ent = ImageDescriptionElements.CalculateLocalBrightnessEntropy(grayImg, ptsTable, props.Radius);
				res.Add(ent);

				//var tempImg = ImageConversion.CreateBmpFromGrayscaleTable(ent.Values);
				//tempImg.Save("img_ent.png");
			}
			if (props.UseLocalEdgeOrientation)
			{
				var edgeOrient = ImageDescriptionElements.CalculateLocalEdgeOrientation(grayImg, ptsTable, props.Radius);
				res.AddRange(edgeOrient);
			}

			return res;
		}

		/// <summary>
		/// Calculates elements for the given image as the whole.
		/// </summary>
		/// <param name="img">Input image.</param>
		/// <param name="props">Image description properties.</param>
		/// <returns>List of description entries.</returns>
		public static List<DescriptionEntry> CalculateDescriptionElements(Bitmap img, ImageDescriptionProperties props)
		{
			var log = LogWriter.Instance();
			var res = new List<DescriptionEntry>();

			var isGrayscale = ImageConversion.IsGrayscale(img);
			var grayImg = new Bitmap(img);
			if (!isGrayscale)
			{
				//log.WriteLine("ImageConversion.ConvertToGrayscale");
				ImageConversion.ConvertToGrayscale(grayImg);
			}

			// calculate image features at all image pixels.
			log.WriteLine("Calculate features for all image pixels");
			if (props.UseLocalBrightness || props.UseLocalColor)
			{
				var color = ImageDescriptionElements.CalculateLocalMeans(img, props.Radius, isGrayscale);
				res.AddRange(color);
			}
			if (props.UseLocalBrightnessVariance)
			{
				var var = ImageDescriptionElements.CalculateLocalBrightnessVariance(grayImg, props.Radius);
				res.Add(var);
			}
			if (props.UseLocalBrightnessVariance)
			{
				var ent = ImageDescriptionElements.CalculateLocalBrightnessEntropy(grayImg, props.Radius);
				res.Add(ent);
			}
			if (props.UseLocalEdgeOrientation)
			{
				var edgeOrient = ImageDescriptionElements.CalculateLocalEdgeOrientation(grayImg, props.Radius);
				res.AddRange(edgeOrient);
			}

			return res;
		}
		#endregion
	}

	/// <summary>
	/// [molecule]
	/// 
	/// Class to represent features of some selected image pixels. Can also be used to store features of all image pixels.
	/// Warning: Coordinates of pixels are not stored.
	/// </summary>
	[Serializable]
	public class SparseImageDescription
	{
		#region - Properties. -
		/// <summary>
		/// List of properties for each pixel.
		/// </summary>
		public List<float[]> PixelProperties { get; set; }
		/// <summary>
		/// Names of pixels features in their consequitive order.
		/// </summary>
		public string FeaturesHeader { get; set; }
		/// <summary>
		/// Name of the image under consideration.
		/// </summary>
		public string ImageName { get; set; }
		/// <summary>
		/// Final error of the vector quantization algorithm.
		/// If quantization wasn't performed then the value is undefined.
		/// </summary>
		public float QuantizationError { get; set; }
		#endregion

		#region - Constructor & Destructor. -
		public SparseImageDescription(List<float[]> props, string header, string name)
		{
			PixelProperties = props;
			FeaturesHeader = header;
			ImageName = name;
		} 
		//~SparseImageDescription ()
		//{
		//    if (PixelProperties != null) PixelProperties.Clear();
		//    if (FeaturesHeader != null) FeaturesHeader = "";
		//    if (ImageName != null) ImageName = "";
		//}
		#endregion

		/// <summary>
		///	[molecule]
		/// 
		/// Creates description form the given file using provided description properties.
		/// </summary>
		/// <param name="file">Name of the image file.</param>
		/// <param name="props">Image description properties.</param>
		/// <returns>Sparse image description.</returns>
		public static SparseImageDescription CreateFromFile (string file, ImageDescriptionProperties props)
		{
			var log = LogWriter.Instance();
			log.Enabled = false;
			log.WriteLine("Creating description for:\t" + file);

			if (!props.UseLocalEdgeOrientation2x2 && !props.UseLocalEdgeOrientation4x4)
			{
				//
				// create description based upon color and/or edges.
				var img = new Bitmap(file);
				List<Point> pts;
				var descr = ImageDescription.CreateFromBitmap(img, props, out pts);
				descr.ImageName = file;

				if (pts.Count > 0)
				{
					return CreateFromImageDescription(descr, props, pts);
				}
				else
				{
					Size size = props.MaxImageDimension > 0 ?
								StructMath.ResizeReduceOnly(img.Size, props.MaxImageDimension)
								:
								img.Size;
					return CreateFromImageDescription(descr, props, size);
				}
			}

			//
			// create edge orientation description from 2x2 splitting.
			var sideTileCount = props.UseLocalEdgeOrientation2x2 ? 2 : 4;
			return ImageDescriptionElements.CreateSparseEdgeDescription(file, props.Radius, sideTileCount);
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Create [SparseImageDescription] on the basis of the given image description for the selected set of the points.
		/// </summary>
		/// <param name="iDescr">Image description.</param>
		/// <param name="props">Image description properties.</param>
		/// <param name="pts">Set of points at which the image description features are calculated.</param>
		/// <returns>Sparse image description.</returns>
		public static SparseImageDescription CreateFromImageDescription (ImageDescription iDescr, ImageDescriptionProperties props, List<Point> pts)
		{
			var log = LogWriter.Instance();
			log.Enabled = false;

			#region - Perform vector quantization. -
			SparseImageDescription descr;
			if (props.UseVectorQuantization)
			{
				string header;
				var kMap = new KohonenMap();
				var netDim = (int)Math.Sqrt(props.MaxQuantizationVectorsCount);
				kMap.Dimensions = new Size(netDim, netDim);
				kMap.TrainingData = iDescr.ConvertToPixelProperties(pts, out header);
				log.WriteLine("TrainBatch");
				kMap.TrainBatch(KohonenMap.BATCH_TRAINING_EPOCHS, false);

				var resWeights = kMap.GetResultingWeights();
				log.WriteLine("new SparseImageDescription");
				descr = new SparseImageDescription(resWeights, header, iDescr.ImageName);
				descr.QuantizationError = kMap.TrainingError[kMap.TrainingError.Count-1];
			}
			else
			{
				string header;
				var resWeights = iDescr.ConvertToPixelProperties(pts, out header);
				log.WriteLine("new SparseImageDescription");
				descr = new SparseImageDescription(resWeights, header, iDescr.ImageName);
				descr.QuantizationError = float.NaN;
			}
			#endregion

			return descr;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Create [SparseImageDescription] on the basis of the given image description for all image pixels.
		/// </summary>
		/// <param name="iDescr">Image description.</param>
		/// <param name="props">Image description properties.</param>
		/// <param name="argSize">Size of the image.</param>
		/// <returns>Sparse image description.</returns>
		public static SparseImageDescription CreateFromImageDescription(ImageDescription iDescr, ImageDescriptionProperties props, Size argSize)
		{
			var log = LogWriter.Instance();
			log.Enabled = false;

			SparseImageDescription descr;

			#region - Perform vector quantization. -
			var size = new Size(iDescr.Entries[0].Values.GetLength(1), iDescr.Entries[0].Values.GetLength(0));

			if (props.UseVectorQuantization)
			{	// use vector quantization via Kohonen maps.
				string header;
				var kMap = new KohonenMap();
				var netDim = (int)Math.Sqrt(props.MaxQuantizationVectorsCount);
				kMap.Dimensions = new Size(netDim, netDim);
				kMap.TrainingData = iDescr.ConvertToPixelProperties(size, out header);
				log.WriteLine("TrainBatch");
				kMap.TrainBatch(KohonenMap.BATCH_TRAINING_EPOCHS, false);

				var resWeights = kMap.GetResultingWeights();
				log.WriteLine("new SparseImageDescription");
				descr = new SparseImageDescription(resWeights, header, iDescr.ImageName);
				descr.QuantizationError = kMap.TrainingError[kMap.TrainingError.Count - 1];
			}
			else
			{	// do not perform vector quantization.
				string header;
				var resWeights = iDescr.ConvertToPixelProperties(size, out header);
				log.WriteLine("new SparseImageDescription");
				descr = new SparseImageDescription(resWeights, header, iDescr.ImageName);
				descr.QuantizationError = float.NaN;
			}
			#endregion

			#region - Perform final averaging. -
			if (props.UseVectorAveraging)
			{
				var tempAvg = VectorMath.MeanVector(descr.PixelProperties);
				descr.PixelProperties = new List<float[]>();
				descr.PixelProperties.Add(tempAvg);
			}
			#endregion

			return descr;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Writes pixel features into the given stream.
		/// </summary>
		/// <param name="writer">Stream to write.</param>
		/// <param name="writeHeader">Indicates whether header containing features names should be written.</param>
		public void Write(StreamWriter writer, bool writeHeader)
		{
			//
			// write description name.
			writer.WriteLine(ImageName);

			//
			// write features' names.
			if (writeHeader) writer.WriteLine(FeaturesHeader);
			
			//
			// Write pixel's features.
			foreach (var props in PixelProperties)
			{
				string line = "" + props[0];
				for (int i = 1; i < props.Length; i++)
				{
					line += "\t" + props[i];
				}
				writer.WriteLine(line);
			}
		}
	}

	/// <summary>
	/// [atomic]
	/// 
	/// Structure to define parameters for image description calculation.
	/// </summary>
	public struct ImageDescriptionProperties
	{
		/// <summary>
		/// Name of the points detector (see ImageAnalysis.POINT_DETECTORS] array for possible options).
		/// </summary>
		public string PointDetectorName;
		/// <summary>
		/// Indicates whether local mean colors should be calculated.
		/// </summary>
		public bool UseLocalColor;
		/// <summary>
		/// Indicates whether local mean brightness property should be calculated.
		/// </summary>
		public bool UseLocalBrightness;
		/// <summary>
		/// Indicates whether local brightness entropy property should be calculated.
		/// </summary>
		public bool UseLocalBrightnessEntropy;
		/// <summary>
		/// Indicates whether local brightness variance property should be calculated.
		/// </summary>
		public bool UseLocalBrightnessVariance;
		/// <summary>
		/// Indicates whether local edge orientation information should be calculated.
		/// </summary>
		public bool UseLocalEdgeOrientation;
		/// <summary>
		/// Indicates whether local edge orientation information should be calculated for each image quad.
		/// </summary>
		public bool UseLocalEdgeOrientation2x2;
		/// <summary>
		/// Indicates whether local edge orientation information should be calculated for each image quad.
		/// </summary>
		public bool UseLocalEdgeOrientation4x4;
		/// <summary>
		/// Indicates whether vector quantization should be performed.
		/// </summary>
		public bool UseVectorQuantization;
		/// <summary>
		/// Indicates whether resulting description vectors should be averaged.
		/// </summary>
		public bool UseVectorAveraging;
		/// <summary>
		/// Neighborhood radius for local parameters calculation.
		/// </summary>
		public int Radius;
		/// <summary>
		/// Defines maximal number of vectors yielded by vector quantization procedure.
		/// </summary>
		public int MaxQuantizationVectorsCount;
		/// <summary>
		/// Defines maximal dimension for any image size. Used to reduce initial image size.
		/// </summary>
		public int MaxImageDimension;
	}

	/// <summary>
	/// [molecule]
	/// 
	/// Class to implement calculation of image description components and provide utility methods.
	/// </summary>
	[Serializable]
	public class ImageDescriptionElements
	{
		#region - String constants defining description properties' names. -
		public static string LOCAL_MEAN_COLOR = "LocalMeanColor";
		public static string LOCAL_MEAN_RED = "LocalMeanRed";
		public static string LOCAL_MEAN_GREEN = "LocalMeanGreen";
		public static string LOCAL_MEAN_BLUE = "LocalMeanBlue";
		public static string LOCAL_MEAN_BRIGHTNESS = "LocalMeanBrightness";
		public static string LOCAL_BRIGHTNESS_VARIANCE = "LocalBrightnessVariance";
		public static string LOCAL_BRIGHTNESS_ENTROPY = "LocalBrightnessEntropy";
		public static string LOCAL_EDGE_ORIENTATION = "LocalEdgeOrientation";
		public static string POINT_DETECTOR_RESPONSE = "PointDetectorResponse"; 
		#endregion

		#region - Local mean brightness and color. -
		/// <summary>
		/// [molecule]
		/// 
		/// Calculates local means for brightness (if [isGrayscale] == true) or R, G, and B colors (otherwise)
		///		for the set of selected pixels and with neighborhood of the given size.
		/// </summary>
		/// <param name="img">Input image</param>
		/// <param name="points">Set of points of interest</param>
		/// <param name="radius">Neightborhood radius.</param>
		/// <param name="isGrayscale">Indicates whether the input image is grayscale.</param>
		/// <returns>List of description entries.</returns>
		public static List<DescriptionEntry> CalculateLocalMeans(Bitmap img, float[,] points, int radius, bool isGrayscale)
		{
			var res = new List<DescriptionEntry>();
			if (isGrayscale)
			{
				var descr = new DescriptionEntry(LOCAL_MEAN_BRIGHTNESS, img.Size);
				var data = ImageConversion.ConvertTo2DTable(img, 0);
				descr.Values = MatrixMath.CalculateLocalStats(data, points, radius, new MeanCalculator());
				res.Add(descr);
			}
			else
			{	// the image is color.
				res = CalculateLocalColorMeans(img, points, radius);
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates local means for brightness (if [isGrayscale] == true) or R, G, and B colors (otherwise)
		///		for all image pixels and with neighborhood of the given size.
		/// </summary>
		/// <param name="img">Input image</param>
		/// <param name="radius">Neightborhood radius.</param>
		/// <param name="isGrayscale">Indicates whether the input image is grayscale.</param>
		/// <returns>List of description entries.</returns>
		public static List<DescriptionEntry> CalculateLocalMeans(Bitmap img, int radius, bool isGrayscale)
		{
			var res = new List<DescriptionEntry>();
			if (isGrayscale)
			{
				var descr = new DescriptionEntry(LOCAL_MEAN_BRIGHTNESS, img.Size);
				var data = ImageConversion.ConvertTo2DTable(img, 0);
				descr.Values = MatrixMath.CalculateLocalStats(data, radius, new MeanCalculator());
				res.Add(descr);
			}
			else
			{	// the image is color.
				res = CalculateLocalColorMeans(img, radius);
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates local means for brightness for the set of selected pixels and with neighborhood of the given size.
		/// </summary>
		/// <param name="img">Input image</param>
		/// <param name="points">Set of points of interest</param>
		/// <param name="radius">Neightborhood radius.</param>
		/// <param name="isGrayscale">Indicates whether the input image is grayscale.</param>
		/// <returns>Description entry.</returns>
		public static DescriptionEntry CalculateLocalBrightness(Bitmap img, float[,] points, int radius, bool isGrayscale)
		{
			var descr = new DescriptionEntry(LOCAL_MEAN_BRIGHTNESS, img.Size);
			byte[,] data = isGrayscale ? 
					ImageConversion.ConvertTo2DTable(img, 0) 
					:
					ImageConversion.ConvertToGrayscaleTable(img);

			//descr.Values = MatrixMath.GetLocalMean(data, points, radius);
			descr.Values = MatrixMath.CalculateLocalStats(data, points, radius, new MeanCalculator());
			return descr;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates local means for brightness for all pixels and with neighborhood of the given size.
		/// </summary>
		/// <param name="img">Input image</param>
		/// <param name="radius">Neightborhood radius.</param>
		/// <param name="isGrayscale">Indicates whether the input image is grayscale.</param>
		/// <returns>Description entry.</returns>
		public static DescriptionEntry CalculateLocalBrightness(Bitmap img, int radius, bool isGrayscale)
		{
			var descr = new DescriptionEntry(LOCAL_MEAN_BRIGHTNESS, img.Size);

			byte[,] data = isGrayscale ? 
							ImageConversion.ConvertTo2DTable(img, 0) 
							: 
							ImageConversion.ConvertToGrayscaleTable(img);

			//descr.Values = MatrixMath.GetLocalMean(data, radius);
			descr.Values = MatrixMath.CalculateLocalStats(data, radius, new MeanCalculator());
			return descr;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates local means for R, G, and B colors for the set of selected pixels and with neighborhood of the given size.
		/// </summary>
		/// <param name="img">Input image</param>
		/// <param name="points">Set of points of interest</param>
		/// <param name="radius">Neightborhood radius.</param>
		/// <returns>List of description entries.</returns>
		public static List<DescriptionEntry> CalculateLocalColorMeans(Bitmap img, float[,] points, int radius)
		{
			var r = ImageConversion.ConvertTo2DTable(img, 0);
			var g = ImageConversion.ConvertTo2DTable(img, 1);
			var b = ImageConversion.ConvertTo2DTable(img, 2);

			//var descrR = new DescriptionEntry(LOCAL_MEAN_RED, img.Size);
			//var descrG = new DescriptionEntry(LOCAL_MEAN_GREEN, img.Size);
			//var descrB = new DescriptionEntry(LOCAL_MEAN_BLUE, img.Size);
			var descrR = new DescriptionEntry { Name = LOCAL_MEAN_RED };
			var descrG = new DescriptionEntry { Name = LOCAL_MEAN_GREEN };
			var descrB = new DescriptionEntry { Name = LOCAL_MEAN_BLUE };

			//descrR.Values = MatrixMath.GetLocalMean(r, points, radius);
			//descrG.Values = MatrixMath.GetLocalMean(g, points, radius);
			//descrB.Values = MatrixMath.GetLocalMean(b, points, radius);
			descrR.Values = MatrixMath.CalculateLocalStats(r, points, radius, new MeanCalculator());
			descrG.Values = MatrixMath.CalculateLocalStats(g, points, radius, new MeanCalculator());
			descrB.Values = MatrixMath.CalculateLocalStats(b, points, radius, new MeanCalculator());

			var res = new List<DescriptionEntry> {descrR, descrG, descrB};

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates local means for R, G, and B colors for all image pixels and with neighborhood of the given size.
		/// </summary>
		/// <param name="img">Input image</param>
		/// <param name="radius">Neightborhood radius.</param>
		/// <returns>List of description entries.</returns>
		public static List<DescriptionEntry> CalculateLocalColorMeans(Bitmap img, int radius)
		{
			var r = ImageConversion.ConvertTo2DTable(img, 0);
			var g = ImageConversion.ConvertTo2DTable(img, 1);
			var b = ImageConversion.ConvertTo2DTable(img, 2);

			var descrR = new DescriptionEntry { Name = LOCAL_MEAN_RED };
			var descrG = new DescriptionEntry { Name = LOCAL_MEAN_GREEN };
			var descrB = new DescriptionEntry { Name = LOCAL_MEAN_BLUE };

			descrR.Values = MatrixMath.CalculateLocalStats(r, radius, new MeanCalculator());
			descrG.Values = MatrixMath.CalculateLocalStats(g, radius, new MeanCalculator());
			descrB.Values = MatrixMath.CalculateLocalStats(b, radius, new MeanCalculator());

			var res = new List<DescriptionEntry> {descrR, descrG, descrB};

			return res;
		}
		#endregion

		#region - Brightness variance. -
		/// <summary>
		/// [molecule]
		/// 
		/// Calculates local brightness variance for the set of selected pixels and with neighborhood of the given size.
		/// Warning: The image should be grayscale.
		/// </summary>
		/// <param name="img">Input image</param>
		/// <param name="points">Set of points of interest</param>
		/// <param name="radius">Neightborhood radius.</param>
		/// <returns>List of description entries.</returns>
		public static DescriptionEntry CalculateLocalBrightnessVariance(Bitmap img, float[,] points, int radius)
		{
			var res = new DescriptionEntry(LOCAL_BRIGHTNESS_VARIANCE, img.Size);
			var data = ImageConversion.ConvertTo2DTable(img, 0);
			res.Values = MatrixMath.CalculateLocalStats(data, points, radius, new VarianceCalculator());

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates local brightness variance for the input image with neighborhood of the given size.
		/// Warning: The image should be grayscale.
		/// </summary>
		/// <param name="img">Input image</param>
		/// <param name="radius">Neightborhood radius.</param>
		/// <returns>List of description entries.</returns>
		public static DescriptionEntry CalculateLocalBrightnessVariance(Bitmap img, int radius)
		{
			var res = new DescriptionEntry(LOCAL_BRIGHTNESS_VARIANCE, img.Size);
			var data = ImageConversion.ConvertTo2DTable(img, 0);
			//res.Values = MatrixMath.GetLocalVariance(data, radius);
			res.Values = MatrixMath.CalculateLocalStats(data, radius, new VarianceCalculator());

			return res;
		} 
		#endregion

		#region - Local entropy. -
		/// <summary>
		/// [molecule]
		/// 
		/// Calculates local brightness entropy for the set of selected pixels and with neighborhood of the given size.
		/// Warning: The image should be grayscale.
		/// </summary>
		/// <param name="img">Input image</param>
		/// <param name="points">Set of points of interest</param>
		/// <param name="radius">Neightborhood radius.</param>
		/// <returns>List of description entries.</returns>
		public static DescriptionEntry CalculateLocalBrightnessEntropy(Bitmap img, float[,] points, int radius)
		{
			var res = new DescriptionEntry(LOCAL_BRIGHTNESS_ENTROPY, img.Size);
			var data = ImageConversion.ConvertTo2DTable(img, 0);
			//res.Values = MatrixMath.GetLocalEntropy(data, points, radius);
			res.Values = MatrixMath.CalculateLocalStats(data, points, radius, new EntropyCalculator());

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates local brightness entropy for the input image with neighborhood of the given size.
		/// Warning: The image should be grayscale.
		/// </summary>
		/// <param name="img">Input image</param>
		/// <param name="radius">Neightborhood radius.</param>
		/// <returns>List of description entries.</returns>
		public static DescriptionEntry CalculateLocalBrightnessEntropy(Bitmap img, int radius)
		{
			var res = new DescriptionEntry(LOCAL_BRIGHTNESS_ENTROPY, img.Size);
			var data = ImageConversion.ConvertTo2DTable(img, 0);
			res.Values = MatrixMath.CalculateLocalStats(data, radius, new EntropyCalculator());

			return res;
		}
		#endregion

		#region - Edge orientation. -
		/// <summary>
		/// [molecule]
		/// 
		/// Calculates responses for 8 different edge orioentation masks.
		/// </summary>
		/// <param name="grayImg">Input *grayscale* image.</param>
		/// <param name="points">Set of points of interest.</param>
		/// <param name="radius">Mask radius.</param>
		/// <returns>List of responses.</returns>
		public static List<DescriptionEntry> CalculateLocalEdgeOrientation(Bitmap grayImg, float[,] points, int radius)
		{
			var res = CalculateLocalEdgeOrientation(grayImg, radius);
			
			//
			// apply point filtering.
			foreach (var entry in res)
			{
				entry.Values = Atomics.MatrixMath.PointsFilter(entry.Values, points);
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates responses for 8 different edge orioentation masks.
		/// </summary>
		/// <param name="grayImg">Input *grayscale* image.</param>
		/// <param name="radius">Mask radius.</param>
		/// <returns>List of responses.</returns>
		public static List<DescriptionEntry> CalculateLocalEdgeOrientation(Bitmap grayImg, int radius)
		{
			//
			// calculated smoothed edge orientation masks.
			int maskSize = radius * 2 + 1;
			//List<float[,]> masks = ImageAnalysis.EdgeOrientationMaskGenerator.CreateMasks8Lines(maskSize, sigma);
			var masks = ImageAnalysis.EdgeOrientationMaskGenerator.CreateGaborMasks8Lines(maskSize);
			var img = (Bitmap)grayImg.Clone();
			ImageFilter.EdgeDetector.Sobel(img, 0.0f);
			var imgTable = ImageConversion.ConvertTo2DTableFloat(img, 0);

			//
			// convolve input image with each mask to calculate responses, which reflect edge orientations.
			var res = new List<DescriptionEntry>();
			for (int i = 0; i < masks.Count; i++)
			{
				var entry = new DescriptionEntry();
				entry.Name = LOCAL_EDGE_ORIENTATION + "["  + i + "]";
				entry.Values = Generic.Convolve(imgTable, masks[i]);
				res.Add(entry);
			}

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates responses for 8 different centered edge orientation masks using quad-splitting (see Atomics.MatrixMath.QuadroSplit method).
		/// </summary>
		/// <param name="grayImg">Input *grayscale* image.</param>
		/// <param name="radius">Mask radius.</param>
		/// <returns>List of responses for each quad.</returns>
		public static List<DescriptionEntry> CalculateLocalCenteredEdgeOrientation(Bitmap grayImg, int radius)
		{
			int maskSize = radius*2 + 1;
			var masks = ImageAnalysis.EdgeOrientationMaskGenerator.CreateMasks8LinesCentered(maskSize);
			
			var img = (Bitmap)grayImg.Clone();
			ImageFilter.EdgeDetector.Sobel(img, 0.0f);
			var imgTable = ImageConversion.ConvertTo2DTableFloat(img, 0);

			//
			// calculate edge orientation responses for quads.
			var quads = Atomics.MatrixMath.QuadroSplit(imgTable);
			var res = new List<DescriptionEntry>();
			foreach (var quad in quads)
			{
				res.AddRange(CalculateEdgeOrientation(quad, masks));
			}
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates responses for 8 different centered edge orientation masks using quad-splitting (see Atomics.MatrixMath.QuadroSplit method).
		/// </summary>
		/// <param name="grayImg">Input *grayscale* image.</param>
		/// <param name="radius">Mask radius.</param>
		/// <returns>List of responses for each quad.</returns>
		public static List<DescriptionEntry> CalculateLocalCenteredEdgeOrientation4x4(Bitmap grayImg, int radius)
		{
			int maskSize = radius * 2 + 1;
			var masks = ImageAnalysis.EdgeOrientationMaskGenerator.CreateMasks8LinesCentered(maskSize);

			var img = (Bitmap)grayImg.Clone();
			ImageFilter.EdgeDetector.Sobel(img, 0.0f);
			var imgTable = ImageConversion.ConvertTo2DTableFloat(img, 0);

			//
			// calculate edge orientation responses for quads.
			var quads = Atomics.MatrixMath.QuadroSplit(imgTable);
			var res = new List<DescriptionEntry>();
			foreach (var quad in quads)
			{
				var subQuads = Atomics.MatrixMath.QuadroSplit(quad);
				foreach (var subQuad in subQuads)
				{
					res.AddRange(CalculateEdgeOrientation(subQuad, masks));
				}
			}
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates edge orientation responses for the given data table and creates appropriate list of image description entries.
		/// </summary>
		/// <param name="data">Input data.</param>
		/// <param name="masks">Array of edge orientation masks.</param>
		/// <returns>List of description entires, containing responses.</returns>
		public static List<DescriptionEntry> CalculateEdgeOrientation (float[,] data, List<float[,]> masks)
		{
			var res = new List<DescriptionEntry>();
			
			//
			// calculate responses for given data.
			for (int i = 0; i < masks.Count; i++)
			{
				var entry = new DescriptionEntry();
				entry.Name = LOCAL_EDGE_ORIENTATION + "[" + i + "]";
				entry.Values = Generic.Convolve(data, masks[i]);

				// cut down small values.
				var avg = Atomics.MatrixMath.Mean(entry.Values);
				var max = Atomics.MatrixMath.Max(entry.Values);
				var thr = Math.Min(avg * avg, max / 2);
				entry.Values = Atomics.MatrixMath.CutDown(entry.Values, thr);

				// add to result.
				res.Add(entry);
			}

			return res;
		}
		#endregion

		#region - Creation. -
		/// <summary>
		/// [molecule]
		/// 
		/// Calculates mean response for centered edges orientation for image quads.
		/// </summary>
		/// <param name="img">Input image.</param>
		/// <param name="radius">Edge orientation mask radius.</param>
		/// <param name="sideTileCount">Number of tiles per each image side.</param>
		/// <returns>Resulting sparse description.</returns>
		public static SparseImageDescription CreateSparseEdgeDescription (Bitmap img, int radius, int sideTileCount)
		{
			//
			// calculate edge orientation responses for image quads.
			var tempImg = new Bitmap(img);
			ImageConversion.ConvertToGrayscale(tempImg);
			var descr = sideTileCount == 2
					? CalculateLocalCenteredEdgeOrientation(tempImg, radius)
					: CalculateLocalCenteredEdgeOrientation4x4(tempImg, radius);

			//
			// calculate mean response for each quad.
			var tilesCount = sideTileCount*sideTileCount;
			var props = new List<float[]>();
			var count = descr.Count / tilesCount;
			var idx = 0;
			for (int i = 0; i < tilesCount; i++)
			{
				var tempV = new float[count];
				for (int j = 0; j < count; j++, idx++)
				{
					var mean = Atomics.MatrixMath.Mean(descr[idx].Values);
					tempV[j] = mean;
				}
				tempV = VectorMath.NormalizeL2(tempV);
				props.Add(tempV);
			}

			//
			// create result.
			var header = "0\t22.5\t45\t72.5\t90\t112.5\t135\t157.5";
			var res = new SparseImageDescription(props, header, "");
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates mean response for centered edges orientation for image quads.
		/// </summary>
		/// <param name="filename">Input image filename.</param>
		/// <param name="radius">Edge orientation mask radius.</param>
		/// <returns>Resulting sparse description.</returns>
		public static SparseImageDescription CreateSparseEdgeDescription(string filename, int radius, int sideTileCount)
		{
			var img = new Bitmap(filename);
			var res = CreateSparseEdgeDescription(img, radius, sideTileCount);
			res.ImageName = filename;
			return res;
		}
		#endregion

		#region - Conversion and information extraction. -
		/// <summary>
		/// [molecule]
		/// 
		/// Converts existing entries into a list of features for each pixel which coordinates are included into [points].
		/// </summary>
		/// <param name="entries">List of description entries.</param>
		/// <param name="points">Array of pixels coordinates to consider.</param>
		/// <param name="header">Out string which contains name of pixel features.</param>
		/// <returns>List of pixel features vectors.</returns>
		public static float[,] ConvertToPixelPropertiesTable(List<DescriptionEntry> entries, List<Point> points, out string header)
		{
			var res = new float[points.Count, entries.Count];

			#region - Fill-in resulting table.
			for (int i = 0; i < points.Count; i++)
			{
				Point tempP = points[i];
				for (int j = 0; j < entries.Count; j++)
				{
					if (entries[j].Values != null && entries[j].Values.Length > 0)
					{
						res[i, j] = entries[j].Values[tempP.Y, tempP.X];
					}
					else
					{
						res[i, j] = entries[j].Value;
					}
				}
			} 
			#endregion

			header = GetPixelFeaturesHeader(entries);

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Converts existing entries into a list of features for all pixels.
		/// </summary>
		/// <param name="entries">List of description entries.</param>
		/// <param name="size">Image size in pixels.</param>
		/// <param name="header">Out string which contains name of pixel features.</param>
		/// <returns>List of pixel features vectors.</returns>
		public static float[,] ConvertToPixelPropertiesTable(List<DescriptionEntry> entries, Size size, out string header)
		{
			var res = new float[size.Height * size.Width, entries.Count];

			#region - Fill-in resulting table.
			int count = 0;
			for (int i = 0; i < size.Height; i++)
			{
				for (int j = 0; j < size.Width; j++, count++)
				{
					for (int k = 0; k < entries.Count; k++)
					{
						if (entries[k].Values != null && entries[k].Values.Length > 0)
						{
							res[count, k] = entries[k].Values[i, j];
						}
						else
						{
							res[count, k] = entries[k].Value;
						}
					}
				}
			}
			#endregion

			header = GetPixelFeaturesHeader(entries);

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Returns string containing names of pixel features in their consequetive order delimitered by tabulations.
		/// </summary>
		/// <param name="entries">List of entries</param>
		/// <returns>Header string.</returns>
		public static string GetPixelFeaturesHeader (List<DescriptionEntry> entries)
		{
			string header = entries[0].Name;
			for (int i = 1; i < entries.Count; i++)
			{
				header += "\t" + entries[i].Name;
			}
			return header;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Converts list of [SparseImageDescription] into the list of [TrainingSample] for classification and categorization problems.
		///	If categories info is not null then only those sparse image descriptions are converted which present in categories info.
		///	Otherwise all image descriptions are converted.
		/// </summary>
		/// <param name="descrs">List of sparse image descriptions.</param>
		/// <param name="catInfo">Images' categories. If null then ClassID for each training samples will be set to -1.</param>
		/// <returns>List of training samples.</returns>
		public static List<TrainingSample> ConvertToTrainingSamples (List<SparseImageDescription> descrs, Dictionary<string, string> catInfo)
		{
			var res = new List<TrainingSample>();
			foreach (var descr in descrs)
			{
				if (catInfo != null && !catInfo.ContainsKey(descr.ImageName)) continue;

				var sample = new TrainingSample
				             	{
				             		Name = descr.ImageName,
				             		Data = Atomics.MatrixMath.CreateFromRowsList(descr.PixelProperties),
				             	};
				sample.ClassID = catInfo == null? -1 : int.Parse(catInfo[descr.ImageName]);

				res.Add(sample);
			}
			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Converts list of [SparseImageDescription] into the list of [TrainingSample] for classification and categorization problems.
		///	Resulting [TrainingSample] entries all has their [ClassID] set to -1 which might be the case of forming data for testing a classification algorithm.
		/// </summary>
		/// <param name="descrs">List of sparse image descriptions.</param>
		/// <returns>List of training samples.</returns>
		public static List<TrainingSample> ConvertToTrainingSamples(List<SparseImageDescription> descrs)
		{
			var res = new List<TrainingSample>();
			foreach (var descr in descrs)
			{

				var sample = new TrainingSample
				{
					Name = descr.ImageName,
					Data = Atomics.MatrixMath.CreateFromRowsList(descr.PixelProperties),
				};
				sample.ClassID = -1;

				res.Add(sample);
			}
			return res;
		}

		#endregion

		#region - Descriptions checking operations. -
		/// <summary>
		/// [molecule]
		/// 
		/// Performs cross checking of the given list of sparse image descriptions:
		///		For each description [descr] the closest description is found from [descrs - descr].
		/// </summary>
		/// <param name="descrs">List of descriptions to check.</param>
		/// <param name="doEdgeAlignment">Indicates whether edge orientation properties should be aligned for better rotation invariance.</param>
		/// <returns>Checking accuracy: the rate of correctly found.</returns>
		public static float CrossCheck (List<SparseImageDescription> descrs, bool doEdgeAlignment)
		{
			float res = 0.0f;

			foreach (var descr in descrs)
			{
				// todo: for each image description [descr] find the closest description from [descrs - descr]
				// todo: write algorithm for alignment of edge orientation props in different descriptions.
			}

			return res;
		}

		#endregion

		#region - File save and load. -
		/// <summary>
		/// Saves given list of descriptions into the prescribed file.
		/// </summary>
		/// <param name="descrs">List of sparse image descriptions.</param>
		/// <param name="filename">Filename.</param>
		public static void Save (IList<SparseImageDescription> descrs, string filename)
		{
			using (var writer = new StreamWriter(filename))
			{
				//
				// Write descriptions for each image.
				foreach (var descr in descrs)
				{
					descr.Write(writer, true);
					writer.WriteLine();
				}
			}
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Performs serialization of the given list of sparse descriptions and writes results into the given file.
		/// </summary>
		/// <param name="descrs">List of sparse descriptions</param>
		/// <param name="filename">Filename</param>
		public static void Serialize(IList<SparseImageDescription> descrs, string filename)
		{
			IFormatter formatter = new BinaryFormatter();
			Stream stream = new FileStream(filename, FileMode.Create, FileAccess.Write, FileShare.None);
			formatter.Serialize(stream, descrs);
			stream.Close();
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Performs deserialization of list of sparse descriptions and from the given file.
		/// </summary>
		/// <param name="filename">Filename.</param>
		/// <returns>Deserialized list of sparse descriptions.</returns>
		public static List<SparseImageDescription> Deserialize (string filename)
		{
			IFormatter formatter = new BinaryFormatter();
			Stream stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.None);
			var descrs = (List<SparseImageDescription>)formatter.Deserialize(stream);
			stream.Close();

			return descrs;
		}
		#endregion

		#region - Maths operations. -
		/// <summary>
		/// [molecule]
		/// 
		/// Calculates all pair-wise distances for the given array of descriptions.
		/// </summary>
		/// <param name="descriptions">Array of descriptions.</param>
		/// <param name="distMeasure">Distance measure.</param>
		/// <returns>Distance matrix.</returns>
		public static float[,] CalculateDistances (SparseImageDescription[] descriptions, DistanceMeasure2D distMeasure)
		{
			int size = descriptions.Length;
			var dists = new float[size, size];

			var tables = new List<float[,]>();
			for (int i = 0; i < size; i++)
			{
				tables.Add(Atomics.MatrixMath.CreateFromRowsList(descriptions[i].PixelProperties));
			}

			for (int i = 0; i < size; i++)
			{
				dists[i,i] = 0.0f;
				for (int j = i+1; j < size; j++)
				{
					//dists[i, j] = dists[j, i] = Atomics.MatrixMath.TableDistance(tables[i], tables[j]);
					dists[i, j] = dists[j, i] = distMeasure(tables[i], tables[j]);
				}
			}
			tables.Clear();

			return dists;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Calculates all pair-wise distances between descriptions from two arrays.
		/// Returns distance matrix D, where D[i,j] denotes table distance from descr1[i] to descr2[j].
		/// </summary>
		/// <param name="descr1">1st array of descriptions.</param>
		/// <param name="descr2">2nd array of descriptions.</param>
		/// <param name="distMeasure">Distance measure.</param>
		/// <returns>Distance matrix.</returns>
		public static float[,] CalculateDistances(SparseImageDescription[] descr1, SparseImageDescription[] descr2, DistanceMeasure2D distMeasure)
		{
			int size1 = descr1.Length, size2 = descr2.Length;
			var dists = new float[size1, size2];

			//
			// convert descriptions into tables.
			var tables1 = new List<float[,]>();
			for (int i = 0; i < size1; i++)
			{
				tables1.Add(Atomics.MatrixMath.CreateFromRowsList(descr1[i].PixelProperties));
			}
			var tables2 = new List<float[,]>();
			for (int i = 0; i < size2; i++)
			{
				tables2.Add(Atomics.MatrixMath.CreateFromRowsList(descr2[i].PixelProperties));
			}

			//
			// calculate distances.
			for (int i = 0; i < size1; i++)
			{
				for (int j = 0; j < size2; j++)
				{
					//dists[i, j] = Atomics.MatrixMath.TableDistance(tables1[i], tables2[j]);
					dists[i, j] = distMeasure(tables1[i], tables2[j]);
				}
			}
			tables1.Clear();
			tables2.Clear();

			return dists;
		}
		#endregion

		#region - Processing of collections of image descriptions.
		/// <summary>
		/// [molecule]
		/// 
		/// Calculates detailed stats for the given list of description. The resulting data contains:
		/// - stats for the whole list of descriptions.
		/// - stats for each image.
		/// - stats for mean feature values for each image.
		/// - distances stats.
		/// </summary>
		/// <param name="descrs">Input list of sparse descriptions.</param>
		/// <param name="distMeasure">Distance measure.</param>
		/// <returns>Dictionary of stats with their names.</returns>
		public static Dictionary<string, Stats> CalculateDetailedStats (List<SparseImageDescription> descrs, DistanceMeasure2D distMeasure)
		{
			//
			// 0. calculate stats for the whole list of descriptions.
			var feats = new List<float[]>();
			var featsHeader = descrs[0].FeaturesHeader.Split('\t');
			foreach(var descr in descrs)
			{
				feats.AddRange(descr.PixelProperties);
			}
			var featsCols = Atomics.MatrixMath.ConvertToColumnsList(Atomics.MatrixMath.CreateFromRowsList(feats));

			var res = new Dictionary<string, Stats>();
			const string prefix = "The whole list, [{0}] feature stats";
			for (var i = 0; i < featsHeader.Length; i++)
			{
				var key = string.Format(prefix, featsHeader[i]);
				res.Add(key, VectorMath.CalculateStats(featsCols[i]));
			}

			//
			// 1. calculate stats for each image.
			const string prefix2 = "Image [{0}], feature [{1}] stats";
			var means = new List<float[]>();
			foreach (var descr in descrs)
			{
				var dCols = Atomics.MatrixMath.ConvertToColumnsList(Atomics.MatrixMath.CreateFromRowsList(descr.PixelProperties));

				var tempMeans = new float[dCols.Count];
				for (int i = 0; i < dCols.Count; i++)
				{
					var key = string.Format(prefix2, descr.ImageName, featsHeader[i]);
					var value = VectorMath.CalculateStats(dCols[i]);
					res.Add(key, value);
					tempMeans[i] = value.Mean;
				}
				means.Add(tempMeans);
			}

			//
			// 1.5 calculate stats for mean feature values for each image.
			var meanCols = Atomics.MatrixMath.ConvertToColumnsList(Atomics.MatrixMath.CreateFromRowsList(means));
			const string prefix3 = "Mean feature [{0}] stats";
			for (var i = 0; i < featsHeader.Length; i++)
			{
				var key = string.Format(prefix3, featsHeader[i]);
				res.Add(key, VectorMath.CalculateStats(meanCols[i]));
			}

			//
			// 2. calculate distances stats.
			var dists = CalculateDistances(descrs.ToArray(), distMeasure);
			var triU = Atomics.MatrixMath.ConvertToVectorTriUIgnoreDiag(dists);
			var distStats = VectorMath.CalculateStats(triU);
			res.Add("Distance stats", distStats);

			return res;
		}

		/// <summary>
		/// [molecule]
		/// 
		/// Get string representation of the detailed stats obtained using [CalculateDetailedStats] method.
		/// </summary>
		/// <param name="descrs">Input list of sparse descriptions.</param>
		/// <param name="distMeasure">Distance measure.</param>
		/// <returns>String representation of all detailed stats.</returns>
		public static List<string[]> GetDetailedStatsStrings(List<SparseImageDescription> descrs, DistanceMeasure2D distMeasure)
		{
			var stats = CalculateDetailedStats(descrs, distMeasure);

			var res = new List<string[]>();
			foreach (var stat in stats)
			{
				var tempList = new List<string>();
				tempList.Add(stat.Key);
				tempList.AddRange(stat.Value.GetStatsString().Split('\t'));

				res.Add(tempList.ToArray());
			}
			return res;
		}
		#endregion

		public class Deprecated
		{
			public static List<BasicDescriptionEntry> GetBasicDescriptionEntriesGrayscale(Bitmap img, float[,] points, int radius)
			{
				int height = img.Height, width = img.Width;
				var gray = ImageConversion.ConvertTo2DTable(img, 0);

				var vars = MentalAlchemy.Atomics.MatrixMath.GetLocalVariance(gray, points, radius);
				var means = MentalAlchemy.Atomics.MatrixMath.GetLocalMean(gray, points, radius);
				var entropy = MentalAlchemy.Atomics.MatrixMath.GetLocalEntropy(gray, points, radius);

				var entries = new List<BasicDescriptionEntry>();
				for (int i = 0; i < height; ++i)
				{
					for (int j = 0; j < width; ++j)
					{
						if (points[i, j] <= 0.0) { continue; }

						var entry = new BasicDescriptionEntry
						{
							Interest = points[i, j],
							Coord = new Point(j, i),
							Entropy = entropy[i, j]
						};

						entry.MeanR = entry.MeanG = entry.MeanB = means[i, j];
						entry.BrightnessVariance = vars[i, j];

						entries.Add(entry);
					}	// for (int j = 0; j < width; ++j)
				}	// for (int i = 0; i < height; ++i)
				return entries;
			}

			public static List<BasicDescriptionEntry> GetRndBasicDescriptionEntriesGrayscale(Bitmap img, int radius, Random rand, float randomPointsRate)
			{
				int height = img.Height, width = img.Width;
				var gray = ImageConversion.ConvertTo2DTable(img, 0);

				var vars = MentalAlchemy.Atomics.MatrixMath.GetLocalVariance(gray, radius);
				var means = MentalAlchemy.Atomics.MatrixMath.GetLocalMean(gray, radius);
				var entropy = MentalAlchemy.Atomics.MatrixMath.GetLocalEntropy(gray, radius);

				var entries = new List<BasicDescriptionEntry>();
				for (int i = 0; i < height; ++i)
				{
					for (int j = 0; j < width; ++j)
					{
						//
						// generate random points.
						if (randomPointsRate > rand.NextDouble())
						{
							var entry = new BasicDescriptionEntry
							{
								Interest = 0.0f,
								Coord = new Point(j, i),
								Entropy = entropy[i, j]
							};

							entry.MeanR = entry.MeanG = entry.MeanB = means[i, j];
							entry.BrightnessVariance = vars[i, j];

							entries.Add(entry);
						}
					}	// for (int j = 0; j < width; ++j)
				}	// for (int i = 0; i < height; ++i)

				return entries;
			}

			public static List<BasicDescriptionEntry> GetBasicDescriptionEntriesColor(Bitmap img, float[,] points, int radius)
			{
				int height = img.Height, width = img.Width;
				var r = ImageConversion.ConvertTo2DTable(img, 0);
				var g = ImageConversion.ConvertTo2DTable(img, 1);
				var b = ImageConversion.ConvertTo2DTable(img, 2);
				var gray = ImageConversion.ConvertToGrayscaleTable(img);

				var meansR = MentalAlchemy.Atomics.MatrixMath.GetLocalMean(r, points, radius);
				var meansG = MentalAlchemy.Atomics.MatrixMath.GetLocalMean(g, points, radius);
				var meansB = MentalAlchemy.Atomics.MatrixMath.GetLocalMean(b, points, radius);
				var vars = MentalAlchemy.Atomics.MatrixMath.GetLocalVariance(gray, points, radius);
				var entropy = MentalAlchemy.Atomics.MatrixMath.GetLocalEntropy(gray, points, radius);

				var entries = new List<BasicDescriptionEntry>();
				for (int i = 0; i < height; ++i)
				{
					for (int j = 0; j < width; ++j)
					{
						if (points[i, j] > 0.0)
						{
							var entry = new BasicDescriptionEntry
							{
								Interest = points[i, j],
								Coord = new Point(j, i),
								Entropy = entropy[i, j],
								MeanR = meansR[i, j],
								MeanG = meansG[i, j],
								MeanB = meansB[i, j],
								BrightnessVariance = vars[i, j]
							};

							entries.Add(entry);
						}
					}	// for (int j = 0; j < width; ++j)
				}	// for (int i = 0; i < height; ++i)
				return entries;
			}

			public static List<BasicDescriptionEntry> GetRndBasicDescriptionEntriesColor(Bitmap img, int radius, Random rand, float randomPointsRate)
			{
				int height = img.Height, width = img.Width;
				var r = ImageConversion.ConvertTo2DTable(img, 0);
				var g = ImageConversion.ConvertTo2DTable(img, 1);
				var b = ImageConversion.ConvertTo2DTable(img, 2);
				var gray = ImageConversion.ConvertToGrayscaleTable(img);

				var meansR = Atomics.MatrixMath.GetLocalMean(r, radius);
				var meansG = Atomics.MatrixMath.GetLocalMean(g, radius);
				var meansB = Atomics.MatrixMath.GetLocalMean(b, radius);
				var vars = Atomics.MatrixMath.GetLocalVariance(gray, radius);
				var entropy = Atomics.MatrixMath.GetLocalEntropy(gray, radius);

				var entries = new List<BasicDescriptionEntry>();
				// generate random points.
				for (int i = 0; i < height; ++i)
				{
					for (int j = 0; j < width; ++j)
					{
						if (randomPointsRate > rand.NextDouble())
						{
							var entry = new BasicDescriptionEntry
							{
								Interest = 0.0f,
								Coord = new Point(j, i),
								Entropy = entropy[i, j],
								MeanR = meansR[i, j],
								MeanG = meansG[i, j],
								MeanB = meansB[i, j],
								BrightnessVariance = vars[i, j]
							};

							entries.Add(entry);
						}
					}	// for (int j = 0; j < width; ++j)
				}	// for (int i = 0; i < height; ++i)

				return entries;
			}
		}
	}

	/// <summary>
	/// Class for a description entry.
	/// Can be a single value or a table. Also has a name property.
	/// </summary>
	[Serializable]
	public class DescriptionEntry
	{
		/// <summary>
		/// Name of the description entry.
		/// </summary>
		public string Name { get; set; }
		/// <summary>
		/// Description entry value (if this entry is a single value).
		/// </summary>
		public float Value { get; set; }
		/// <summary>
		/// Description entry values (if this entry is a vector or a table).
		/// </summary>
		public float[,] Values { get; set; }

		/// <summary>
		/// Default constructor.
		/// </summary>
		public DescriptionEntry ()
		{
			Name = "";
		}

		/// <summary>
		/// Constructor.
		/// 
		/// If values size is 1x1 then [Values] array is not created and the
		///		entry is considered to be a single value.!
		/// </summary>
		/// <param name="name">Entry's name.</param>
		/// <param name="size">Values size.</param>
		public DescriptionEntry (string name, Size size)
		{
			Name = name;
			if (size.Height>1 || size.Width>1)
			{
				Values = new float[size.Height, size.Width];
			}
		}
	}

	#region - Deprecated. -
	namespace Deprecated
	{
		[Serializable]
		public class BasicDescriptionEntry
		{
			public float MeanR { get; set; }		// mean red component in a neighborhood.
			public float MeanG { get; set; }		// mean green component in a neighborhood.
			public float MeanB { get; set; }		// mean blue component in a neighborhood.
			public float Entropy { get; set; }		// entropy of graylevels in a neighborhood.
			public float Interest { get; set; }		// value of Harris detector.
			public float BrightnessVariance { get; set; }
			public Point Coord { get; set; }
			public List<float> OrientationVector { get; set; }	// aligned orientation vector.

			public bool Equals(BasicDescriptionEntry descr)
			{
				if (BrightnessVariance != descr.BrightnessVariance) return false;
				if (Entropy != descr.Entropy) return false;
				if (Interest != descr.Interest) return false;
				if (MeanB != descr.MeanB) return false;
				if (MeanG != descr.MeanG) return false;
				if (MeanR != descr.MeanR) return false;

				throw new NotImplementedException("bool Equals(BasicDescriptionEntry descr)");

				return true;
			}

			public static BasicDescriptionEntry Create(float[] v)
			{
				var entry = new BasicDescriptionEntry
				{
					BrightnessVariance = v[0],
					Entropy = v[1],
					Interest = v[2],
					MeanB = v[3],
					MeanG = v[4],
					MeanR = v[5],
					Coord = Point.Empty
				};

				var tempV = new float[v.Length - 6];
				Array.Copy(v, 6, tempV, 0, tempV.Length);
				entry.OrientationVector = new List<float>(tempV);

				return entry;
			}
		}

		[Serializable]
		public class BasicImageDescription
		{
			private string filename = "";

			#region - Properties. -
			public List<BasicDescriptionEntry> Entries { get; set; }

			public string Filename
			{
				get { return filename; }
				set { filename = value; }
			}

			public static bool UseGaussian { get; set; }
			#endregion

			#region - Static methods. -
			public static BasicImageDescription Create(float[,] table)
			{
				var descr = new BasicImageDescription
				{
					Entries = new List<BasicDescriptionEntry>()
				};

				var height = table.GetLength(0);
				for (int i = 0; i < height; ++i)
				{
					var entry = BasicDescriptionEntry.Create(MentalAlchemy.Atomics.MatrixMath.GetRow(table, i));
					descr.Entries.Add(entry);
				}

				return descr;
			}

			public static BasicImageDescription Create(Bitmap img, int radius)
			{
				var src = new Bitmap(img);
				//if (UseGaussian == true) { src = ImageFilter.GaussianBlur(src, radius / 3.0f); }

				bool isGreyscale = ImageConversion.IsGrayscale(src);
				float[,] harris = isGreyscale
										? ImageAnalysis.HarrisDetector.Calculate(src, 1.0f)
										: ImageAnalysis.HarrisDetector.CalculateColor(src, 1.0f);

				var descr = new BasicImageDescription
				{
					Entries = new List<BasicDescriptionEntry>()
				};

				const float randomPointsRate = 0.1f;
				var rand = new Random();

				if (isGreyscale)
				{
					#region - Create grayscale description. -
					descr.Entries = ImageDescriptionElements.Deprecated.GetBasicDescriptionEntriesGrayscale(src, harris, radius);

					#region - Create random description -
					if (descr.Entries.Count == 0)
					{
						descr.Entries = ImageDescriptionElements.Deprecated.GetRndBasicDescriptionEntriesGrayscale(src, radius, rand, randomPointsRate);
					}
					#endregion
					#endregion
				}
				else
				{
					#region - Create color description. -
					descr.Entries = ImageDescriptionElements.Deprecated.GetBasicDescriptionEntriesColor(src, harris, radius);

					#region - Create random description -
					if (descr.Entries.Count == 0)
					{
						descr.Entries = ImageDescriptionElements.Deprecated.GetRndBasicDescriptionEntriesColor(src, radius, rand, randomPointsRate);
					}
					#endregion
					#endregion
				}	// if (isGreyscale)

				return descr;
			}

			public static BasicImageDescription CreateFull(Bitmap img, int radius)
			{
				var src = new Bitmap(img);

				bool isGreyscale = ImageConversion.IsGrayscale(src);
				float[,] harris = isGreyscale
										? ImageAnalysis.HarrisDetector.Calculate(src, 1.0f)
										: ImageAnalysis.HarrisDetector.CalculateColor(src, 1.0f);
				int height = harris.GetLength(0), width = harris.GetLength(1);

				var descr = new BasicImageDescription
				{
					Entries = new List<BasicDescriptionEntry>()
				};

				if (isGreyscale)
				{
					float[,] gray = ImageConversion.ConvertToGrayscaleTableFloat(src);

					for (int i = 0; i < height; ++i)
					{
						for (int j = 0; j < width; ++j)
						{
							var entry = new BasicDescriptionEntry
							{
								Interest = harris[i, j],
								Coord = new Point(j, i)
							};

							Dictionary<float, int> nStats = MentalAlchemy.Atomics.MatrixMath.GetNeighborhoodStats(gray, entry.Coord, radius);

							// calculate real neighborhood size.
							int size = 0;
							foreach (int count in nStats.Values) { size += count; }
							float size_1 = 1.0f / size;

							// calculate (1) mean brightness (also equals to meanR, meanG, and meanB) in a neighborhood.
							//           (2) brightness variance in a neighborhood.
							//           (3) brightness entropy in a neighborhood.
							entry.MeanB = 0.0f;
							entry.Entropy = 0.0f;
							entry.BrightnessVariance = 0.0f;
							foreach (KeyValuePair<float, int> pair in nStats)
							{
								float temp = pair.Key;
								entry.MeanB += temp * pair.Value;
								entry.BrightnessVariance += temp * temp * pair.Value;

								float p = pair.Value * size_1;
								entry.Entropy -= (float)(p * Math.Log(p, 2));
							}
							entry.MeanR = entry.MeanG = entry.MeanB = entry.MeanB * size_1;
							entry.BrightnessVariance = entry.BrightnessVariance * size_1 - entry.MeanB * entry.MeanB;

							descr.Entries.Add(entry);
						}	// for (int j = 0; j < width; ++j)
					}	// for (int i = 0; i < height; ++i)
				}
				else
				{
					float[,] r = ImageConversion.ConvertTo2DTableFloat(src, 0);
					float[,] g = ImageConversion.ConvertTo2DTableFloat(src, 1);
					float[,] b = ImageConversion.ConvertTo2DTableFloat(src, 2);
					byte[,] grey = ImageConversion.ConvertToGrayscaleTable(src);

					for (int i = 0; i < height; ++i)
					{
						for (int j = 0; j < width; ++j)
						{
							var entry = new BasicDescriptionEntry
							{
								Interest = harris[i, j],
								Coord = new Point(j, i)
							};

							Dictionary<float, int> rStats = MentalAlchemy.Atomics.MatrixMath.GetNeighborhoodStats(r, entry.Coord, radius);
							Dictionary<float, int> gStats = MentalAlchemy.Atomics.MatrixMath.GetNeighborhoodStats(g, entry.Coord, radius);
							Dictionary<float, int> bStats = MentalAlchemy.Atomics.MatrixMath.GetNeighborhoodStats(b, entry.Coord, radius);

							// calculate real neighborhood size.
							int size = 0;
							foreach (int count in rStats.Values) { size += count; }
							float size_1 = 1.0f / size;

							// calculate (1) meanR, meanG, and meanB in a neighborhood.
							entry.MeanR = 0.0f;
							foreach (KeyValuePair<float, int> pair in rStats) { entry.MeanR += pair.Key * pair.Value; }
							entry.MeanR = entry.MeanR * size_1;

							entry.MeanG = 0.0f;
							foreach (KeyValuePair<float, int> pair in gStats) { entry.MeanG += pair.Key * pair.Value; }
							entry.MeanG = entry.MeanG * size_1;

							entry.MeanB = 0.0f;
							foreach (KeyValuePair<float, int> pair in bStats) { entry.MeanB += pair.Key * pair.Value; }
							entry.MeanB = entry.MeanB * size_1;

							// calculate (2) brightness variance in a neighborhood.
							//           (3) brightness entropy in a neighborhood.
							var greyStats = MentalAlchemy.Atomics.MatrixMath.GetNeighborhoodStats(grey, entry.Coord, radius);

							entry.Entropy = 0.0f;
							entry.BrightnessVariance = 0.0f;
							float meanGrey = 0.0f;
							foreach (var pair in greyStats)
							{
								float temp = pair.Key;
								meanGrey += temp;
								entry.BrightnessVariance += temp * temp;

								float p = pair.Value * size_1;
								entry.Entropy -= (float)(p * Math.Log(p, 2));
							}
							meanGrey *= size_1;
							entry.BrightnessVariance = entry.BrightnessVariance * size_1 - meanGrey * meanGrey;

							descr.Entries.Add(entry);
						}	// for (int j = 0; j < width; ++j)
					}	// for (int i = 0; i < height; ++i)
				}	// if (isGreyscale)

				return descr;
			}

			public static bool Equals(BasicImageDescription descr1, BasicImageDescription descr2)
			{
				if (descr1.Entries == null || descr2.Entries == null || descr1.Entries.Count != descr2.Entries.Count) return false;

				for (int i = 0; i < descr2.Entries.Count; ++i)
				{
					if (!descr1.Entries[i].Equals(descr2.Entries[i])) { return false; }
				}

				return true;
			}

			public static float[,] ConvertToTableFloat(BasicImageDescription descr)
			{
				if (descr.Entries == null) { return null; }

				var res = new float[descr.Entries.Count, 6];

				int i = 0;
				foreach (BasicDescriptionEntry entry in descr.Entries)
				{
					res[i, 0] = entry.BrightnessVariance;
					res[i, 1] = entry.Entropy;
					res[i, 2] = entry.Interest;
					res[i, 3] = entry.MeanB;
					res[i, 4] = entry.MeanG;
					res[i, 5] = entry.MeanR;

					++i;
				}

				return res;
			}
			#endregion

			public bool Equals(ImageDescription descr)
			{
				return Equals(this, descr);
			}

			public float[,] ConvertToTableFloat()
			{
				return ConvertToTableFloat(this);
			}
		}

		public class DescriptionDistance
		{
			public static float Calculate(BasicImageDescription d1, BasicImageDescription d2)
			{
				return 0.5f * (CalculateOneSide(d1, d2) + CalculateOneSide(d2, d1));
			}

			/// <summary>
			/// Calculates distance from the 1st desription to the 2nd.
			/// </summary>
			/// <param name="d1"></param>
			/// <param name="d2"></param>
			/// <returns></returns>
			public static float CalculateOneSide(BasicImageDescription d1, BasicImageDescription d2)
			{
				float res = 0.0f;

				int size2 = d2.Entries.Count;
				if (size2 > 0)
				{
					foreach (BasicDescriptionEntry entry in d1.Entries)
					{
						float minDist = 100.0f;

						for (int i = 0; i < size2; ++i)
						{
							float temp = Calculate(entry, d2.Entries[i]);
							if (minDist > temp)
							{
								minDist = temp;
								//minIdx = i;
							}
						}

						res += minDist;
					}
				}
				else
				{
					throw new Exception("Can't calculate distance between d1 and an empty description d2");
				}

				return res / d1.Entries.Count;
			}

			public static float Calculate(BasicDescriptionEntry d1, BasicDescriptionEntry d2)
			{
				float res = 0.0f;

				// todo: define a radius for each field in [BasicDescriptionEntry] which defines bounds for just noticable difference between values (probably, via Weber's law).
				//float factor = 1.0 / (Const.GRAYLEVEL_MAX * Const.GRAYLEVEL_MAX);
				float temp = (d1.BrightnessVariance - d2.BrightnessVariance);
				res += temp * temp;

				// skip coordinates to obtain invariance for feature position.
				//temp = EuclidianMeasure.DistanceSqr(d1.Coord, d2.Coord);
				//res += temp * temp;

				//factor = 1.0/8.0;
				temp = (d1.Entropy - d2.Entropy);
				res += temp * temp;

				//factor = 1.0 / 255.0;
				temp = (d1.Interest - d2.Interest);
				res += temp * temp;

				temp = (d1.MeanB - d2.MeanB);
				res += temp * temp;

				temp = (d1.MeanG - d2.MeanG);
				res += temp * temp;

				temp = (d1.MeanR - d2.MeanR);
				res += temp * temp;

				return (float)Math.Sqrt(res);
			}
		}
	}
	#endregion
}
